package edu.upol.paradise.compiler;

import java.util.HashMap;

import org.apache.log4j.Logger;

import edu.upol.paradise.runtime.ParadiseRuntime;
import edu.upol.paradise.runtime.RuntimeExceptions;
import edu.upol.paradise.compiler.types.Nil;
import edu.upol.paradise.compiler.types.ParadiseObject;
import edu.upol.paradise.compiler.types.ParadiseString;
import edu.upol.paradise.compiler.types.ParadiseSymbol;
import edu.upol.paradise.compiler.types.VariablePassingWrapper;

public abstract class ParadiseCompiledBlockStub implements ParadiseCompiledBlock{
	static final Logger log = Logger.getLogger(ParadiseCompiledBlockStub.class);
	
	public static enum ExecutionStatus {
		IDLE, STARTED, SUCCESSFUL, INTERRUPTED, FAILED
	}
	
	protected HashMap<String, ParadiseObject> closureROVariables = new HashMap<String, ParadiseObject>();
	protected HashMap<String, ParadiseObject> blockVariables     = new HashMap<String, ParadiseObject>();
	protected ParadiseObject			      currentTop;
	
	protected int 			  lastExecutionPoint    = 0;
	protected ParadiseRuntime runtime				= null;
	protected Object[]		  currentLocals         = null;
	
	private ExecutionStatus  status 			   = ExecutionStatus.IDLE;
	private ParadiseObject   result 			   = Nil.NIL;
	private Throwable		 exception 		   	   = null;
	private volatile boolean execute 		       = true;
	
	public ParadiseCompiledBlockStub(){
		
	}
	
	/**
	 * Runs the compiled bytecode
	 * @return
	 * @throws Throwable Exception if exception has happened, Interrupt if thread is interrupted from outside
	 */
	protected abstract ParadiseObject runBytecode()									      throws Throwable;
	
	protected void define(ParadiseSymbol symbol, ParadiseObject value){
		runtime.set(symbol, value);
	}
	
	protected ParadiseObject findSymbolValue(ParadiseSymbol symbol) throws Exception {
		if (blockVariables.containsKey(symbol.toString()))
			return blockVariables.get(symbol.toString());
		if (closureROVariables.containsKey(symbol.toString()))
			return closureROVariables.get(symbol.toString());
		return runtime.findSymbolValue(symbol);
	}
	
	protected void setError(ParadiseString error) throws RuntimeExceptions.UserErrorException{
		runtime.setErrorMessage(error.getValue());
		throw new RuntimeExceptions.UserErrorException();
	}
	
	protected void set(ParadiseSymbol symbol, ParadiseObject value){
		blockVariables.remove(symbol.toString());
		blockVariables.put(symbol.toString(), value);
	}
	
	public void initializeVariables(VariablePassingWrapper wrapper) {
		if (wrapper == null) return;
		
		for (String name : wrapper.getArgNames()){
			blockVariables.put(name, wrapper.getSingleObject(name));
		}
	}
	
	public void run(){
		try {
			synchronized (this){
				status = ExecutionStatus.STARTED;
			}
			ParadiseObject ret = runBytecode();
			successfulExecution(ret);
		} catch (ParadiseInterrupt i){
			haltExecution();
		} catch (Throwable e){
			failedExecution(e);
		} finally {
			execute = true;
		}
	}
	
	public synchronized void requestHalt(){
		execute = false;
	}
	
	private synchronized void successfulExecution(ParadiseObject ret) {
		result = ret;
		status = ExecutionStatus.SUCCESSFUL;
	}
	
	private synchronized void haltExecution() {
		status = ExecutionStatus.INTERRUPTED;
	}

	private synchronized void failedExecution(Throwable e) {
		status = ExecutionStatus.FAILED;
		exception = e;
	}
	
	protected synchronized void markExecutionPoint(int executionPoint, ParadiseObject currentTop) throws ParadiseInterrupt {
		if (!execute)
			throw new ParadiseInterrupt();
		lastExecutionPoint = executionPoint;
		this.currentTop = currentTop;
	}

	public synchronized int getLastExecutionPoint() {
		return lastExecutionPoint;
	}

	public synchronized ExecutionStatus getStatus() {
		return status;
	}

	public ParadiseObject getResult() {
		return result;
	}

	public Throwable getException() {
		return exception;
	}

	public HashMap<String, ParadiseObject> getBlockVariableMappings(){
		return blockVariables;
	}
	
	public HashMap<String, ParadiseObject> getClosureVariables(){
		return closureROVariables;
	}
	
	public void setRuntime(ParadiseRuntime runtime){
		this.runtime = runtime;
	}
	
	public ParadiseObject getTop(){
		return currentTop;
	}
}
